/*
使用方法：
<script setup lang="ts">
import { getCurrentInstance } from 'vue';

const instance = getCurrentInstance();
if (instance) {
  const { $utils } = instance.appContext.config.globalProperties;

  // 现在可以在组件中调用 $utils 上的方法
  $utils.method1();
}
</script>
 */

type CallbackFunction = (id: number, runtime: number, countTimes: number) => void;
type AfterTimeUpFunction = (id: number, usedTime: number, countTimes: number) => void;

export default {

    /**
     * 通过setTimeout模拟setInterval
     * @param id    定时器id，自定义
     * @param aminTime  执行间隔时间
     * @param callback  定时执行的函数，返回callback(id,runtime)，id是定时器的时间，runtime是当前运行的时间
     * @param maxTime   定时器重复执行的最大时长
     * @param afterTimeUp   定时器超时之后的回调函数，返回afterTimeUp(id,usedTime,countTimes)，id是定时器的时间，usedTime是定时器执行的总时间，countTimes是当前定时器运行的回调次数
     */
    runTimer(id: number, aminTime: number, callback: CallbackFunction, maxTime: number, afterTimeUp: AfterTimeUpFunction): void {
        let startTime: number = new Date().getTime(); // 记录开始时间
        let countTimes: number = 0; // 回调执行次数
        let usedTime: number = 0; // 累计使用时间
        let timer: NodeJS.Timeout;

        /**
         * 通过计算 diffTime，可以调整下一次 setTimeout 调用的时间间隔。
         * diffTime 是上一次任务执行所花费的时间超出预期的部分。
         * runtime = aminTime - diffTime; 通过调整 runtime，下次执行时会补偿上次的延迟。
         * 这样做的好处是即使每次调用都稍有延迟，整体的误差会被逐步纠正。
         * @param diffTime
         */
        function timeout(diffTime: number): void { // 主函数，定时器本体
            let runtime: number = aminTime - diffTime; // 计算下一次的执行间隔
            timer = setTimeout(() => {
                countTimes++;
                // 记录上一次的执行的结束时间，如果是首次执行，则为0
                let tmp = startTime;

                // 执行任务，补充：setTimeout的主要误差来源于：程序的执行时间 + 网络传输的时间 + js是单线程的，如果任务比较繁忙，那么还有js的时间循环时间
                callback(id, runtime, countTimes);

                // 记录任务的结束时间
                startTime = new Date().getTime();
                usedTime = startTime - (tmp - diffTime); // 更新累计使用时间

                // 如果累计时间未超过最大时间，则继续，反之结束定时器
                if (usedTime < maxTime) {
                    // 计算本次任务的执行时间：(startTime - tmp)，如果执行时间正好为0，则代表没有误差时间；如果负数，则有误差时间；如果为正数，肯定不能为正数
                    // 下一次定时需要扣除的时间：误差时间（要么为0，要么为负数） + 间隙时间
                    diffTime = (startTime - tmp) - aminTime; // 计算需扣除的时间，并执行下一次的调用
                    timeout(diffTime);
                } else {
                    // 清除定时器，调用超时后的回调函数
                    clearTimeout(timer);
                    afterTimeUp(id, usedTime, countTimes);
                }
            }, runtime);
        }
        // 初始化调用
        timeout(0);
    }

}
